asynx.namespace("xpath");

asynx.xpath = {
	
	query: null,
	scope: document,

	getNodes: function(query) {
		this.query = query;
		var sc = arguments[1];
		this.scope = [sc?sc:document];
		
		var schemas = this.schema(query);
		var validNodes = [];
		
		for (var i=0; i < schemas.length; i++) {
			var schema = schemas[i];
			var info = this.getExpInfo(schema);
			
			if (!info) break;
			
			var sAttr = info.sAttr;
			var sValue = info.sValue;
			var sTagName = info.sTagName == "all" ? "*" : info.sTagName;
			var nodeNum = info.nodeNum;
			var comparator = info.comparator;
			var customSelector = info.customSelector;
			var nodeContain = info.nodeContain;
			var solitaryNode = !!(/\d+/.test(nodeNum));
			
			var filterCriteria = {comparator:comparator, customSelector:customSelector};
		
			var byTag = !!(!sAttr && !sValue && sTagName);
			var byTagAttr = !!(sAttr && !sValue);		
			var byTagAttrValue = !!(sAttr && sValue);		
			var canStoreValidNodes = !!(i+1 == schemas.length);
			
			var found = [];
			
			if (schema.nextQuery.substring(0,1) == "#") {
				var id = schema.nextQuery.substring(1, schema.nextQuery.length);
				found = [document.getElementById(id)];
			}
			else if (sAttr == "id" && sValue) {
				found = [document.getElementById(sValue)];
			}
			else{
				found = asynx.dom.getByTagName(this.scope, sTagName ? sTagName : "*");
			}
			
			if (byTagAttr)
				found = this.filter(found, sAttr, undefined, filterCriteria);
			
			else if (byTagAttrValue) {
				found = this.filter(found, sAttr, sValue, filterCriteria);
			}
			if (nodeContain)
				found = this.filterIfContainsNode(found, nodeContain);
				
			if (canStoreValidNodes) {
				if (solitaryNode) {
				 	validNodes.push(found[nodeNum]);
				}else {
					asynx.merge(validNodes, schema.getAll ? found : [found[0]]);
				}
			};
			
			if (solitaryNode)
				this.scope = [found[nodeNum]];
			else 
				this.scope = schema.getAll ? found : [found[0]];
				
		}
		
		if (customSelector)
			validNodes = this.applyCustomSelector(validNodes, customSelector);
		
		return validNodes;
	},
	
	filterIfContainsNode: function(nodes, nodeName) {
		
		var filtered = [];
		
		for (var x=0; nodes[x]; x++) {
			var node = nodes[x];
			var contains = node.getElementsByTagName(nodeName);
			if (contains.length > 0) filtered.push(node);
			else continue;
		};
		
		return filtered;
	},
	
	applyCustomSelector: function(nodes, customSelector) {
		
		var filtered = [];

		var cssFilters = {
			hidden:true,
			visible:true
		};
		
		// performance...
		if (cssFilters[customSelector]) {
			for (var x = 0; nodes[x]; x++) {
				var node = nodes[x];
				var style = node.style;
				var cssDisplay = style.display;
				var cssVisibility = style.visibility;
				
				var conditions = {
					visible: !!(cssDisplay!="none"&&cssVisibility!="hidden"),
					hidden: !!(cssDisplay=="none"||cssVisibility=="hidden")
				};
				
				if (conditions[customSelector])
					filtered.push(node);
				else continue;
			};
		}else{
			// other filters
			for (var x = 0; nodes[x]; x++) {
				var node = nodes[x];
	
				var conditions = {
					even: !!(x%2==0),
					odd: !!(x%2!=0),
					first: !!(x==0),
					last: !!(x==nodes.length-1),
					parent: !!(node.hasChildNodes())
				};
				
				if (conditions[customSelector])
					filtered.push(node);
				else continue;
			};
		}
		
		return filtered;
	},
	
	filter: function() {
		var nodes = arguments[0];
		var sAttr = arguments[1];
		var sValue = arguments[2];
		var criteria = arguments[3];
		var comparator = criteria.comparator;
		var customSelector = criteria.customSelector;
		var store = [];
		
		var byAttrValue = !!(sAttr && !asynx.isUndef(sValue));
		var byAttr = !!(sAttr && asynx.isUndef(sValue));
		var sType = /^[0-9.,]+$/.test(sValue) ? "digit" : "string";
		
		for (var x = 0; x < nodes.length; x++) {
			var node = nodes[x];
			if (!node) continue;
			
			var attrNode = undefined;
			
			if (byAttrValue||byAttr) {
				// performance
				// ie cant get all attributes by getAttribute
				if (/class|for|checked|action|method/.test(sAttr))
					attrNode=asynx.dom.getAttribute(node, sAttr);			
				else
					attrNode=node.getAttribute(sAttr);
			}
			
			if (byAttr && node) {
				if (attrNode!=undefined) {
					store.push(node);
				}
				else continue;
			};
			
			
			var isString = asynx.isString(attrNode);
			
			if (byAttrValue && attrNode && node && sType == "digit") {
				
				var comparators = {
					digit: {
						"=": !!(parseFloat(attrNode) == parseFloat(sValue)),
						"!=": !!(parseFloat(attrNode) != parseFloat(sValue)),
						">": !!(parseFloat(attrNode) > parseFloat(sValue)),
						"<": !!(parseFloat(attrNode) < parseFloat(sValue)),
						">=": !!(parseFloat(attrNode) >= parseFloat(sValue)),
						"<=": !!(parseFloat(attrNode) <= parseFloat(sValue)),
						"^=": !!(isString && attrNode.substring(0, sValue.length) == sValue),
						"$=": !!(isString && attrNode.substring(attrNode.length-sValue.length, attrNode.length) == sValue),
						"all=": !!(isString && attrNode.indexOf(sValue) != -1)
					}
				};
				
				if (comparators[sType][comparator]) store.push(node);
				else continue;
			}
			
			if (byAttrValue && node && attrNode && sType == "string") {
				var comparators = {
					string: {
						"=": !!(attrNode == sValue),
						"!=": !!(attrNode != sValue),
						"^=": !!(isString && attrNode.substring(0, sValue.length) == sValue),
						"$=": !!(isString && attrNode.substring(attrNode.length-sValue.length, attrNode.length) == sValue),
						"all=": !!(isString && attrNode.indexOf(sValue) != -1)
					}
				};
				
				if (comparators[sType][comparator])	{
					store.push(node);
				}
				else continue;
			}
			
		}
		
		return store;
	},
	
	schema: function(query) {
		
		if (query.substring(0,1) != "/" && 
				query.substring(0,2) != "//") query += "//" + query;
		
		var siblings = query.split("/");
		siblings.shift();
		
		var getAllFromNext = false;
		var schema = [];
		
		for (var x = 0; x < siblings.length; x++) {
			var sibling = siblings[x];
			
			if (sibling == "") {
				getAllFromNext = true;
				continue;	
			};
			
			var sibling = sibling.replace("*", "all");
			var customSelector;
			var regexCustomSelector = /:([\w-]+)$/; //:even, :odd, ...
			var regexId = /^([\w-]+)?(#[\w-]+)/; //#id
			
			var mCustom = sibling.match(regexCustomSelector);
			if (mCustom) {
				customSelector = mCustom[1];
				sibling = sibling.replace(regexCustomSelector, "");
			};
			
			var mId = sibling.match(regexId);
			if (mId) {
				sibling = mId[2];
			};
			
			schema.push ({ nextQuery:sibling, getAll:getAllFromNext, query:query, customSelector:customSelector });
			getAllFromNext = false;
		}
		return schema;
	},
	
	getExpInfo: function(schema) {
		
		var matchers = [
			/ *([\w-]+)?\[ *@([\w-]+) *(=|<|>|>=|<=|!=|\^=|\$=|all=) *['"]?([\w. -]+|)?['"]? *\]( *\[ *([a-zA-Z-]+) *\])?( *\[ *([\d]+) *\])?$/,
			/ *([\w-]+)?\[ *@([\w-]+) *\]( *\[ *([a-zA-Z-]+) *\])?( *\[ *([\d]+) *\])?$/,
			/ *([\w-]+)( *\[ *([a-zA-Z-]+) *\])?( *\[ *([\d]+) *\])?$/
		];
		
		var rules = [
			{ tag:1, attr:2, comparator:3, _value:4, nodeNum:8, contains:6 },
			{ tag:1, attr:2, nodeNum:6, contains:4 },
			{ tag:1, contains:3, nodeNum:5}
		];
		
		var values = false;
		var nextQuery = schema.nextQuery;
		
		asynx.each(matchers, function(e, x) {
			var m = nextQuery.replace("*=", " all=").match(e);
			if (m) {
				var rule = rules[x];
				
				var sTagName = m[rule.tag];
				var sAttr = m[rule.attr];
				var sValue = m[rule._value];
				var nodeNum = m[rule.nodeNum];
				var comparator = m[rule.comparator];
				var nodeContain = m[rule.contains];
				
				//alert(nextQuery + " : " + sTagName + "-" + sAttr + "-" + sValue + "-" + nodeNum + "-" + nodeContain + "-" + x)
				values = {};
				values = { sAttr:sAttr, sValue:sValue, nodeNum:nodeNum, sTagName:sTagName, 
							comparator:comparator, customSelector:schema.customSelector, nodeContain:nodeContain };
			};
		});
		
		return values;
	}
		
};